package com.fakesmoker.aidawnda.scoring;/**
 * @program: aidawnda-backend
 * @author: lcy
 * @create: 2024-11-09 14:46
 */

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fakesmoker.aidawnda.manager.AiManager;
import com.fakesmoker.aidawnda.model.dto.ai.QuestionAnswerDTO;
import com.fakesmoker.aidawnda.model.dto.question.QuestionContentDto;
import com.fakesmoker.aidawnda.model.entity.App;
import com.fakesmoker.aidawnda.model.entity.Question;
import com.fakesmoker.aidawnda.model.entity.UserAnswer;
import com.fakesmoker.aidawnda.service.QuestionService;
import com.fakesmoker.aidawnda.service.ScoringResultService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 2024/11/9,
 */

/**
 * 自定义评分策略实现类
 * @author: lcy
 */
@ScoringStrategyConfigAnnotation(appType = 0,scoringStrategy =1)
public class AiScoreScoringStrategyImpl implements ScoringStrategy {
    @Resource
    private QuestionService questionService;

    @Resource
    private AiManager aiManager;

    @Resource
    private RedissonClient redissonClient;
    //本地缓存
    private final Cache<String, String> answerCacheMap =
            Caffeine.newBuilder().initialCapacity(1024)
                    // 缓存5分钟移除
                    .expireAfterAccess(365L, TimeUnit.DAYS)
                    .build();
    private static final String AI_ANSWER_LOCK = "AI_ANSWER_LOCK";

private static final String SCORING_SYSTEM_PROMPT = "你是一位极为严谨的判题得分的专家。接收以下信息格式：\n" +
        "应用名称，\n" +
        "【【【应用描述】】】，\n" +
        "题目和用户回答的列表：格式为 [{\"title\": \"题目\",\"answer\": \"用户回答\"}]\n" +
        "请依据上述信息，按如下步骤对用户的答案结果给出得分情况并且进行温柔的评价：\n" +
        "给出明确的评价结果，评价名称应尽量简短，评价描述需尽量详细（不少于 100 字）。\n" +
        "严格按照以下 JSON 格式输出评价名称和评价描述：\n" +
        "{\"resultName\": \"评价名称\", \"resultDesc\": \"评价描述\"}\n" +
        "返回格式必须为标准的 JSON 对象。";

    @Override
    public UserAnswer doScore(List<String> choices, App app) throws InterruptedException {
        Long appId = app.getId();
        String choicesStr = JSONUtil.toJsonStr(choices);
        String cacheKey = buildCacheKey(appId,choicesStr);

        //先走缓存
        String answerJson = answerCacheMap.getIfPresent(cacheKey);
        // 命中缓存则直接返回结果
        if (StrUtil.isNotBlank(answerJson)) {
            UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(choicesStr);
            return userAnswer;
        }
        //缓存未命中，则走数据库
        //细粒度上锁
       RLock lock = redissonClient.getLock(AI_ANSWER_LOCK + cacheKey);
        try {
            // 竞争分布式锁，等待 3 秒，15 秒自动释放
            boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
            if (res){
                // 抢到锁的业务才能执行 AI 调用
                //1.根据id查询到题目和题目结果信息

                Question question=questionService
                        .getOne(Wrappers.lambdaQuery(Question.class)
                                .eq(Question::getAppId, appId));
                String questionContent = question.getQuestionContent();
                List<QuestionContentDto> questionContentDtoList = JSONUtil.toList(questionContent,QuestionContentDto.class);

                String userMessage = getUserMessage(app, choices, questionContentDtoList);

                //2.调用ai获取结果
                String result = aiManager.doSyncStableRequest(SCORING_SYSTEM_PROMPT,userMessage);

                // 解析json
                int start=result.indexOf("{");
                int end=result.lastIndexOf("}");
                result=result.substring(start,end+1);

                // 构造返回值，填充答案对象的属性
                UserAnswer userAnswer=JSONUtil.toBean(result, UserAnswer.class);

                userAnswer.setAppId(appId);
                userAnswer.setAppType(app.getAppType());
                userAnswer.setChoices(JSONUtil.toJsonStr(choices));
                userAnswer.setScoringStrategy(app.getScoringStrategy());

                //将结果存入缓存
                answerCacheMap.put(cacheKey, JSONUtil.toJsonStr(result));
                return userAnswer;

            }else {
                return null;
            }
        } finally {
            if (lock != null && lock.isLocked()) {
                if(lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }



    }

    private String getUserMessage(App app,List<String> choices,List<QuestionContentDto> questionContentDtoList){
        StringBuilder userMessage=new StringBuilder();
        userMessage.append(app.getAppName()).append("\n");
        userMessage.append(app.getAppDesc()).append("\n");
        List<QuestionAnswerDTO> questionAnswerDTOList = new ArrayList<>();
        for (int i = 0; i < questionContentDtoList.size(); i++) {
            QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO();
            //设置题目
            questionAnswerDTO.setTitle(questionContentDtoList.get(i).getTitle());

            //设置选项值
            String value="";
            String choice = choices.get(i);
            for (QuestionContentDto.Option option : questionContentDtoList.get(i).getOptions()) {
                if (option.getKey().equalsIgnoreCase(choice)) {
                    value= option.getValue();
                    questionAnswerDTO.setUserAnswer(value); // 修改为 实际选项的内容

                }
            }
            //questionAnswerDTO.setUserAnswer(choices.get(i));
            questionAnswerDTOList.add(questionAnswerDTO);
        }
        userMessage.append(JSONUtil.toJsonStr(questionAnswerDTOList));
        return userMessage.toString();

    }
    /**
     * 本地缓存的key构建压缩 由appId 和用户的答案组成
     * 因为过长的占用 所以用md5压缩
     * @param appId
     * @param choicesStr
     * @return
     */
    private String buildCacheKey(Long appId, String choicesStr) {
        return DigestUtil.md5Hex(appId + ":" + choicesStr);
    }

    /**
     * 单机sync一致性评分
     * @param choices
     * @param app
     * @return
     * @throws InterruptedException
     */
    public UserAnswer doScoreBySingle(List<String> choices, App app) throws InterruptedException {
        Long appId = app.getId();
        String choicesStr = JSONUtil.toJsonStr(choices);
        String cacheKey = buildCacheKey(appId, choicesStr);

        // 先走缓存
        String answerJson = answerCacheMap.getIfPresent(cacheKey);
        // 命中缓存则直接返回结果
        if (StrUtil.isNotBlank(answerJson)) {
            UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(choicesStr);
            return userAnswer;
        }

        synchronized (this) {
            // 再次检查缓存，防止等待锁的期间其他线程已经完成计算并缓存结果
            answerJson = answerCacheMap.getIfPresent(cacheKey);
            if (StrUtil.isNotBlank(answerJson)) {
                UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
                userAnswer.setAppId(appId);
                userAnswer.setAppType(app.getAppType());
                userAnswer.setScoringStrategy(app.getScoringStrategy());
                userAnswer.setChoices(choicesStr);
                return userAnswer;
            }

            // 1.根据id查询到题目和题目结果信息
            Question question = questionService
                    .getOne(Wrappers.lambdaQuery(Question.class)
                            .eq(Question::getAppId, appId));
            String questionContent = question.getQuestionContent();
            List<QuestionContentDto> questionContentDtoList = JSONUtil.toList(questionContent, QuestionContentDto.class);

            String userMessage = getUserMessage(app, choices, questionContentDtoList);

            // 2.调用ai获取结果
            String result = aiManager.doSyncStableRequest(SCORING_SYSTEM_PROMPT, userMessage);

            // 解析json
            int start = result.indexOf("{");
            int end = result.lastIndexOf("}");
            result = result.substring(start, end + 1);

            // 构造返回值，填充答案对象的属性
            UserAnswer userAnswer = JSONUtil.toBean(result, UserAnswer.class);

            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setChoices(JSONUtil.toJsonStr(choices));
            userAnswer.setScoringStrategy(app.getScoringStrategy());

            // 将结果存入缓存
            answerCacheMap.put(cacheKey, JSONUtil.toJsonStr(result));
            return userAnswer;
        }
    }
}
